package geometry;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.Node;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Matrix4d;
import javax.vecmath.Vector3d;

import constraint.Variable;
import constraint.VariableType;
import foundationgeom.Axis;
import foundationgeom.EulerAngles;
import foundationgeom.Tolerance;
import foundationgeom.Transformation;
import foundationgeom.Vector;
import graph.AbstractNode;
import graph.GNode;
import graph.NodeType;

public class Sphere extends AbstractNode implements Geometry, GNode {
    private Variable radius;
    private CoordSys cs;
    
    public Sphere(double radius, String label) {
        super(NodeType.PART, label);
        cs = new CoordSys(World.getCS(), this);
        this.radius = new Variable(radius, VariableType.SPHERE_RADIUS, this, label);
    }
    
    public Sphere(double radius, Collection<String> labels) {
        super(NodeType.PART, labels);
        cs = new CoordSys(World.getCS(), this);
        this.radius = new Variable(radius, VariableType.SPHERE_RADIUS, this, "");
    }
    
    
    public Sphere(double radius, Collection<String> labels, CoordSys parent) {
        super(NodeType.PART, labels);
        cs = new CoordSys(parent, this);
        this.radius = new Variable(radius, VariableType.SPHERE_RADIUS, this, "");
    }

    public boolean areEqual(Geometry geometry) {
        if (geometry instanceof Sphere == false) {
            return false;
        }
        Sphere sphere= (Sphere) geometry;
        Tolerance tolerance = Tolerance.DEFAULT;
        if(!tolerance.x.epsilonEquals(this.radius.getDoubleValue(),sphere.radius.getDoubleValue())) {
            return false;
        }
        return this.cs.equal(sphere.cs);
    }

    public Sphere copy(CoordSys parent) {
        Sphere sphere = new Sphere(radius.getDoubleValue(), super.getLabels(), parent);
        sphere.cs.setOriginDisplacement(this.cs.getOriginDisplacement());
        sphere.cs.setOrientation(this.cs.getAxialFrame());
        sphere.addLabels(this.getLabels());     
        return sphere;
    }

    public CoordSys getCoordSys() {
        return cs;
    }

    public Vector getScale(Geometry geometry) {
        if (geometry instanceof Sphere) {
            Sphere box = (Sphere) geometry;
            return Vector.create(box.radius.getDoubleValue()/this.radius.getDoubleValue(), 
                    box.radius.getDoubleValue()/this.radius.getDoubleValue(),
                    box.radius.getDoubleValue()/this.radius.getDoubleValue());
        }
        System.out.println("TRYING TO GENERATE SCALE BETWEEN TWO OBJECTS THAT ARE NOT THE SAME TYPE.");
        return null;
    }

    public Transformation getTransformation(Geometry geometry) {
        return this.cs.getTransformationTo(geometry.getCoordSys());
    }

    public List<Node> getTransormedJava3DBox(float red, float blue,
            float green, float scale) {
        Matrix4d matrix = new Matrix4d();
        matrix.setIdentity();
        matrix.setTranslation(new Vector3d(
                cs.getOriginDisplacement().getX()/scale, 
                cs.getOriginDisplacement().getY()/scale, 
                cs.getOriginDisplacement().getZ()/scale));
        
        Transform3D transform3D = new Transform3D(matrix);
        TransformGroup transformGroup = new TransformGroup();
        transformGroup.setTransform(transform3D);
        transformGroup.addChild(getJava3DSphere(red, blue, green, scale));
        List<Node> nodes = new ArrayList<Node>();
        nodes.add(transformGroup);
        return nodes;

    }

    private Node getJava3DSphere(float red, float blue, float green, float scale) {
        Appearance a = new Appearance();
        a.setColoringAttributes(new ColoringAttributes(red,blue,green, ColoringAttributes.NICEST));
        com.sun.j3d.utils.geometry.Sphere b = new com.sun.j3d.utils.geometry.Sphere( 
                radius.getFloatValue()/scale, a);
        return b;
    }

    public Variable getVariable(VariableType type) {
        if (type == VariableType.X_POS || type == VariableType.Y_POS || type == VariableType.Z_POS) {
            return cs.getVariable(type);
        } else if (type == VariableType.SPHERE_RADIUS) {
            return radius;
        } 
        return null;
    }

    public Variable[] getVariables() {
        Variable[] pos = cs.getVariables(); 
        return new Variable[]{radius, pos[0], pos[1], pos[2]};
    }

    public boolean intersects(Geometry geometry) {
        // TODO Auto-generated method stub
        return false;
    }

    public void print() {
        System.out.println("radius="+radius);
        System.out.println(cs.toString());        
    }

    public void rotate(double x, double y, double z) {
        this.cs.rotate(Axis.X, x);
        this.cs.rotate(Axis.Y, y);
        this.cs.rotate(Axis.Z, z);        
    }

    public void scale(double sx, double sy, double sz) {
        radius.setValue((int) (radius.getDoubleValue() * sx));
    }

    public String toVRML() {
        CoordSys center = new CoordSys(this.getCoordSys(), null);
        center.changeParentTo(World.getCS());
        Vector o = center.getOriginDisplacement();
        String s="";
        s = s+"Transform {\n";
        s = s+"  translation ";
        s = s + o.getX();
        s = s + " ";
        s = s + o.getY();
        s = s + " ";
        s = s + o.getZ();
        s = s + "\n rotation     0.0 0.0 1.0  ";
        s = s + Math.toRadians(center.getEulerAngles().getHeading());//3.14 # the rotation around the Z axis by 180 degree";
        s = s + "  \nchildren[\n\tShape {\n\t\tappearance Appearance {\n\t\t\tmaterial Material {";
        s = s + "\n\t\t\t\temissiveColor 1 1 0 \n\t\t\t\tambientIntensity 8 \n\t\t\t\tspecularColor 1 1 0 \n\t\t\t\ttransparency 0\n\t\t\t}\n\t\t}";
        s = s + "\n\t\tgeometry Sphere{";
        s = s + "\n\t\tradius ";
        s = s + radius.getDoubleValue();
        s = s + " ";
        s = s + "\n\t\t}\n\t\t}\n\t]\n\t\n}";
        return s;
    }

    public Geometry transform(CoordSys parent, Transform transformation) {
        Sphere sphere = this.copy(parent);
        Vector o = sphere.cs.getOriginDisplacement();
        Vector t = transformation.transformation.displacement();
        Vector sum = o.add(t);
//        Vector v = transformation.transformation.apply(o);
        sphere.cs.setOriginDisplacement(sum);
        EulerAngles angles = sphere.cs.getEulerAngles();
        EulerAngles transformedAngles = transformation.transformation.apply(angles);
        sphere.cs.setOrientation(transformedAngles);
        sphere.radius.setValue((sphere.radius.getDoubleValue() * transformation.sx));
        return sphere;
    }

    public void translateBy(double dx, double dy, double dz) {
        cs.translate(Vector.create(dx,dy,dz));
    }

    public GNode copy() {
        return (GNode)this.copy(this.cs.getParent());
    } 

    public GNode transformInSiblingCS(Transform transformation, Geometry lhsGeom, Geometry designGeom) {
        Sphere box = this.copy(designGeom.getCoordSys());
        box.cs.changeParentTo(World.getCS());
        return box;
    }

}
